﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace d39
{
    public partial class GridMove : GridPage
    {
        public override string SubTitle
        {
            get { return "Move"; }
        }

        private class MoveCommand : GridCommand
        {
            internal MoveCommand(GridMove page, Player player, IEnumerable<PositionMove> positions, Vector direction)
                : base(page)
            {
                this.player = player;
                this.direction = direction;
                this.positions = positions.ToList();
            }

            private Player player;
            private Vector direction;
            private List<PositionMove> positions;

            private new GridMove GridPage { get { return (GridMove)base.GridPage; } }

            public override string Text
            {
                get
                {
                    string dest;
                    if (positions.Count == 1)
                    {
                        dest = " to " + (positions.Single().Position + direction).ToHumanReadableString();
                    }
                    else if (player.SizeInSquares == 1)
                    {
                        dest = " to {}";
                    }
                    else
                    {
                        // There's more than one place they could go and it's not necessarily the square being hovered over so we can't state it
                        // unambiguously.
                        // Perhaps when SizeInSquares is greater than 1 we should treat each position separately, but that's bad if we have a whole crowd.
                        dest = null;
                    }
                    return "Move " + player.Name + " " + direction.ToHumanReadableMoveString() + dest;
                }
            }

            public override IEnumerable<HalfCoordinate> Cells
            {
                get
                {
                    return from pm in positions
                           from cell in GridPage.getCellsInDirection(pm.Position, player.SizeInSquares, direction)
                           select (HalfCoordinate)cell;
                }
            }

            public override void Invoke(HalfCoordinate coord)
            {
                var position = (from pm in positions
                                where GridPage.getCellsInDirection(pm.Position, player.SizeInSquares, direction).Contains((Coordinate)coord)
                                select pm).Single();
                position.Moves.Add(direction);
                position.Position += direction;
                GridPage.GridChanged(true, false);
            }
        }

        private class UndoMoveCommand : GridCommand
        {
            private PositionMove pm;
            private int count;
            internal UndoMoveCommand(GridMove page, PositionMove pm, int count)
                : base(page)
            {
                this.pm = pm;
                this.count = count;
            }

            public override string Text
            {
                get
                {
                    if (count == pm.Moves.Count)
                    {
                        return "Undo " + pm.Player.Name + "'s move";
                    }
                    else if (count == 1)
                    {
                        return "Undo last square of " + pm.Player.Name + "'s move";
                    }
                    else
                    {
                        return "Undo last " + count + " squares of " + pm.Player.Name + "'s move";
                    }
                }
            }

            public override IEnumerable<HalfCoordinate> Cells
            {
                get
                {
                    var pos = pm.Position;
                    for (int i = 1; i <= count; i++)
                    {
                        pos -= pm.Moves[pm.Moves.Count - i];
                    }
                    yield return pos;
                }
            }

            public override void Invoke(HalfCoordinate coord)
            {
                for (int i = 1; i <= count; i++)
                {
                    pm.Position -= pm.Moves[pm.Moves.Count - i];
                }
                pm.Moves.RemoveRange(pm.Moves.Count - count, count);
                GridPage.GridChanged(true, false);
            }
        }


        private IEnumerable<Coordinate> getCellsInDirection(Coordinate position, int sizeInSquares, Vector direction)
        {
            if (direction == Vector.NW)
            {
                yield return position + Vector.NW;
            }
            if (direction == Vector.NE)
            {
                yield return position + Vector.E * sizeInSquares + Vector.N;
            }
            else if (direction == Vector.SE)
            {
                yield return position + Vector.SE * sizeInSquares;
            }
            else if (direction == Vector.SW)
            {
                yield return position + Vector.S * sizeInSquares + Vector.W;
            }
            else if (direction == Vector.N)
            {
                for (var i = 0; i < sizeInSquares; i++) yield return position + Vector.E * i + Vector.N;
            }
            else if (direction == Vector.E)
            {
                for (var i = 0; i < sizeInSquares; i++) yield return position + Vector.E * sizeInSquares + Vector.S * i;
            }
            else if (direction == Vector.S)
            {
                for (var i = 0; i < sizeInSquares; i++) yield return position + Vector.S * sizeInSquares + Vector.E * i;
            }
            else if (direction == Vector.W)
            {
                for (var i = 0; i < sizeInSquares; i++) yield return position + Vector.S * i + Vector.W;
            }
        }

        private IEnumerable<Coordinate> getCellsForPositions(IEnumerable<PositionMove> positions, Vector direction)
        {
            return from pm in positions
                   from cell in getCellsInDirection(pm.Position, pm.Player.SizeInSquares, direction)
                   select cell;
        }

        private IEnumerable<MoveCommand> getMoveCommands(Player player, Vector direction)
        {
            var positionSets = new List<List<PositionMove>>();
            foreach (var pm in player.GetRevealedPositions(CurrentGrid))
            {
                if (pm.IsFixed) continue;
                foreach (var positionSet in positionSets)
                {
                    var testSet = getCellsForPositions(positionSet, direction);
                    if (!testSet.Intersect(getCellsInDirection(pm.Position, player.SizeInSquares, direction)).Any())
                    {
                        positionSet.Add(pm);
                        goto next;
                    }
                }
                positionSets.Add(new List<PositionMove>() { pm });
            next: ;
            }
            return from positionSet in positionSets select new MoveCommand(this, player, positionSet, direction);
        }

        public override IEnumerable<GridCommand> Commands
        {
            get
            {
                foreach (var undoCmd in
                       from move in CurrentGrid.RevealedMoves
                       from count in Enumerable.Range(1, move.Moves.Count)
                       select new UndoMoveCommand(this, move, count))
                {
                    yield return undoCmd;
                }
                foreach (var moveCmd in
                       from player in CurrentGrid.RevealedPlayers
                       from dir in Moves.Directions
                       from command in getMoveCommands(player, dir)
                       select command)
                {
                    yield return moveCmd;
                }
            }
        }

        protected IEnumerable<PositionMove> GetPositionsToShow()
        {
            if (ShowMoveCompasses.Checked)
            {
                return from p in CurrentGrid.RevealedPlayers
                       from pm in p.GetRevealedPositions(CurrentGrid)
                       where !pm.IsFixed
                       select pm;
            }
            else
            {
                return CurrentGrid.RevealedMoves;
            }
        }

        protected void ShowMoveCompasses_CheckedChanged(object sender, EventArgs e)
        {
            UpdatePanel.Update();
            MoveCompasses.DataBind();
        }

        protected override void UpdateFromGrid(bool playersOrMovesChanged, bool wallTerrainChanged)
        {
            if (playersOrMovesChanged)
            {
                UpdatePanel.Update();
                MoveCompasses.DataBind();
            }
        }

        protected void Move_Command(object sender, ListViewCommandEventArgs e)
        {
            var pm = GetPositionsToShow().ToList()[e.Item.DataItemIndex];
            Vector newMove;
            switch (e.CommandName)
            {
                case "NW": newMove = Vector.NW; break;
                case "N": newMove = Vector.N; break;
                case "NE": newMove = Vector.NE; break;
                case "E": newMove = Vector.E; break;
                case "W": newMove = Vector.W; break;
                case "SW": newMove = Vector.SW; break;
                case "S": newMove = Vector.S; break;
                case "SE": newMove = Vector.SE; break;
                case "undo":
                    foreach (var move in pm.Moves) pm.Position -= move;
                    pm.Moves.Clear();
                    GridChanged(true, false);
                    return;
                case "finish":
                    pm.Moves.Clear();
                    GridChanged(true, false);
                    return;
                default: return;
            }
            pm.Moves.Add(newMove);
            pm.Position += newMove;
            GridChanged(true, false);
        }

        protected string GetPosMoveString(object positionMove)
        {
            var pm = (PositionMove)positionMove;
            return pm.Moves.Any() ? pm.GetMoveString(CurrentGrid.DnDVersion) : pm.Player.Name + " at " + pm.Position.ToHumanReadableString();
        }
    }
}